#include "PlanarReflector.h"



PlanarReflector::PlanarReflector() : mbCullBack(true), rtv(0), dsv(0), width(0), height(0), m_ViewProj(XMFLOAT4X4()), 
									 m_reflect(XMFLOAT4X4()), fx_m_WorldReflectViewProj(0), fx_m_WorldReflect(0), 
									fx_reflector_height(0), fx_tex_shadow_map(0), fx_lights(0), fx_num_lights(0), 
									fx_pEye(0), fx_bShadowed(0), fx_bUsePCSS(0)
{
	
}


PlanarReflector::~PlanarReflector()
{
	try
	{
		if (rtv)
			rtv->Release();
		if (dsv)
			dsv->Release();
	}
	catch(...)
	{
		MessageBoxA(0, "exception thrown in  destructor", 0, 0);
	}
}

void PlanarReflector::init(const char* filename, ID3D11Device* g_device, ID3D11DeviceContext* g_context)
{

	Mesh::init(filename, g_device, g_context);

	// 1. Create Texture
	D3D11_TEXTURE2D_DESC tex_desc;
	ZeroMemory(&tex_desc, sizeof(tex_desc));
	tex_desc.Width = width;
	tex_desc.Height = height;
	tex_desc.MipLevels = 1;
	tex_desc.ArraySize = 1;
	tex_desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
	tex_desc.SampleDesc.Count = 1; tex_desc.SampleDesc.Quality = 0;
	tex_desc.Usage = D3D11_USAGE_DEFAULT;
	tex_desc.BindFlags = D3D11_BIND_SHADER_RESOURCE |  D3D11_BIND_RENDER_TARGET;
	tex_desc.CPUAccessFlags = tex_desc.MiscFlags = 0;

	
	HRESULT hr = createReflectionTexture2D(g_dev(), tex_desc);
	if (FAILED(hr)) MessageBoxA(0, "Error create reflection texture", 0, 0);

	// 2. Create Render Target View
	D3D11_RENDER_TARGET_VIEW_DESC  rtv_desc;
	ZeroMemory(&rtv_desc, sizeof(rtv_desc));
	rtv_desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
	rtv_desc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE2D;
	rtv_desc.Texture2D.MipSlice = 0;

	hr = createRenderTargetView(g_dev(), rtv_desc);
	if (FAILED(hr)) MessageBoxA(0, "Error creating reflections rtv", 0, 0);

	// 3. Create Shader Resource View
	D3D11_SHADER_RESOURCE_VIEW_DESC srv_desc;
	ZeroMemory(&srv_desc, sizeof(srv_desc));
	srv_desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
	srv_desc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D;
	srv_desc.Texture2D.MipLevels = 1;
	srv_desc.Texture2D.MostDetailedMip = 0;
	

	hr = createShaderResourceView(g_dev(), srv_desc);
	if (FAILED(hr)) MessageBoxA(0, "Error creating reflections srv", 0, 0);
	
	/////////////////////////////////////////

	// 1. Create Texture
	ZeroMemory(&tex_desc, sizeof(tex_desc));
	tex_desc.Width = width;
	tex_desc.Height = height;
	tex_desc.MipLevels = 1;
	tex_desc.ArraySize = 1;
	tex_desc.Format = DXGI_FORMAT_R32_TYPELESS;
	tex_desc.SampleDesc.Count = 1; tex_desc.SampleDesc.Quality = 0;
	tex_desc.Usage = D3D11_USAGE_DEFAULT;
	tex_desc.BindFlags = D3D11_BIND_SHADER_RESOURCE |  D3D11_BIND_DEPTH_STENCIL;
	tex_desc.CPUAccessFlags = tex_desc.MiscFlags = 0;

	hr = createDepthTexture2D(g_dev(), tex_desc);
	if (FAILED(hr)) MessageBoxA(0, "Error creating reflections depthtexture", 0, 0);

	// 2. Create Render Target View
	D3D11_DEPTH_STENCIL_VIEW_DESC  dsv_desc;
	ZeroMemory(&dsv_desc, sizeof(dsv_desc));
	dsv_desc.Format = DXGI_FORMAT_D32_FLOAT;
	dsv_desc.Flags = 0;
	dsv_desc.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2D;
	dsv_desc.Texture2D.MipSlice = 0;

	
	hr = this->createDepthStencilView(g_dev(), dsv_desc);
	if (FAILED(hr)) MessageBoxA(0, "Error creating reflections dsv", 0, 0);

	// fx variables
	fx_m_WorldReflectViewProj = getFX()->GetVariableByName("m_WorldReflectViewProj")->AsMatrix();
	fx_m_WorldReflect  =		getFX()->GetVariableByName("m_WorldReflect")->AsMatrix();	
	fx_reflector_height =		getFX()->GetVariableByName("reflector_height")->AsScalar();


	fx_tex_shadow_map		= getFX()->GetVariableByName("tex_shadow_map")->AsShaderResource();
	fx_lights				= getFX()->GetVariableByName("lights");
	fx_num_lights			= getFX()->GetVariableByName("num_lights")->AsScalar();
	fx_pEye					= getFX()->GetVariableByName("pEye")->AsVector();
	fx_bShadowed			= getFX()->GetVariableByName("bShadowed")->AsScalar();
	fx_bUsePCSS				= getFX()->GetVariableByName("bUsePCSS")->AsScalar();
}

void PlanarReflector::startReflectionDraw(const Camera &camera, vector<Light*>& lights)
{
	// save current rtv/dsv
	context()->OMGetRenderTargets(1, &rtv, &dsv);

	// set our own
	setRenderTarget(context());

	// camera 
	XMMATRIX mViewProj = camera.getViewMatrix() * camera.getProjectionMatrix();
	XMStoreFloat4x4(&m_ViewProj, mViewProj);

	// Compute reflection matrix
	float reflector_height = getWorldMatrix(0)._42;
	
	XMMATRIX m_T1 = XMMatrixIdentity();
	m_T1._42 = -reflector_height;

	XMMATRIX m_S  =XMMatrixIdentity();
	m_S._22 = -1.0f;	// reflection across y with -1

	XMMATRIX m_T2 = m_T1;
	m_T2._42 = reflector_height;

	XMMATRIX mReflect = m_T1 * m_S * m_T2;	
	XMStoreFloat4x4(&m_reflect, mReflect);


	//// Reflect light's position, direction

	l_pos.clear();
	l_dir.clear();

	//// store pos/dir in variables to restore later..
	for (unsigned int i=0; i<lights.size(); ++i)
	{
		
		XMFLOAT4 lpos = lights[i]->getPosition();
		XMFLOAT4 ldir = lights[i]->getDirection();

		l_pos.push_back(lpos);
		l_dir.push_back(ldir);
		
		XMVECTOR reflected_lpos = XMVector4Transform(XMLoadFloat4(&lpos), mReflect);
		XMVECTOR reflected_ldir = XMVector4Transform(XMLoadFloat4(&ldir), mReflect);

		XMFLOAT4 refl_pos, refl_dir;
		XMStoreFloat4(&refl_pos, reflected_lpos);
		XMStoreFloat4(&refl_dir, reflected_ldir);

		lights[i]->setPosition(refl_pos.x, refl_pos.y, refl_pos.z);
		lights[i]->setDirection(refl_dir.x, refl_dir.y, refl_dir.z);	
	}
	
	

	// Set reflected pos/dir in light structure
	//
	// Light // mView, shadow_map, light params
	for (unsigned int i=0; i<lights.size(); ++i)
	{
		fx_lights =			getFX()->GetVariableByName("lights")->GetElement(i);
		fx_tex_shadow_map = getFX()->GetVariableByName("tex_shadow_map")->AsShaderResource();
		
		fx_tex_shadow_map->SetResource(	lights[i]->getShadowMapSRV()	);
		fx_lights->SetRawValue(	(void*)&(lights[i]->getLightStructure()), 0, sizeof(Light_Params)	);
	}
	ID3DX11EffectScalarVariable*	fx_shadow_size = getFX()->GetVariableByName("shadow_size")->AsScalar();
	fx_shadow_size->SetInt(lights[0]->getShadowMapSize());
	fx_num_lights->SetInt(lights.size());

}

void PlanarReflector::endReflectionDraw(vector<Light*>& lights)
{
	context()->OMSetRenderTargets(1, &rtv, dsv);

	rtv->Release();
	dsv->Release();

	for (unsigned int i=0; i<lights.size(); i++)
	{
		XMFLOAT4 lpos = l_pos[i];
		XMFLOAT4 ldir = l_dir[i];

		lights[i]->setPosition(lpos.x, lpos.y, lpos.z);
		lights[i]->setDirection(ldir.x, ldir.y, ldir.z);
	}
}

void PlanarReflector::drawReflection(const Mesh* mesh)
{
	
	float reflector_height = getWorldMatrix(0)._42;
	
	XMMATRIX m_T1 = XMMatrixIdentity();
	m_T1._42 = -reflector_height;

	XMMATRIX m_S  = XMMatrixIdentity();
	m_S._22 = -1.0f;	// reflection across y with -1

	XMMATRIX m_T2 = m_T1;
	m_T2._42 = reflector_height;

	XMMATRIX mReflect = m_T1 * m_S * m_T2;	

	
	for (int i=0; i<mesh->getNumSubsets(); i++)
	{

		// Set WRVP Matrix
		XMMATRIX mWVP = mesh->getWorldMatrix(i) * mReflect * XMLoadFloat4x4(&m_ViewProj);
		fx_m_WorldReflectViewProj->SetMatrix((float*)&mWVP);

		XMMATRIX m = mesh->getWorldMatrix(i);

		// set World * Reflect
		fx_m_WorldReflect->SetMatrix((float*)&(mesh->getWorldMatrix(i) * mReflect));

		// set reflector y
		fx_reflector_height->SetFloat(reflector_height);
		fx_bShadowed->SetBool(mesh->isShadowReceiver());
	
		// shadow algorithm
		fx_bUsePCSS->SetBool(false);
	
		ID3DX11EffectScalarVariable*	fx_bLit = getFX()->GetVariableByName("bLit")->AsScalar();
		fx_bLit->SetBool(mesh->isLit());

		mesh->setMaterial(getFX());

		if (mbCullBack)
			mesh->draw(0, getFX(), "Reflection");
		else
			mesh->draw(0, getFX(), "Reflection_CullFront");
	}

}